package com.jc.server.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ConcurrentReferenceHashMap;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;


/*
@to sess https://mdn.mozillademos.org/files/16137/WebRTC_-_Signaling_Diagram.svg

sdp message {type:[video-offer|video-answer],name:'sender's username',target:callee,sdp:'session description proto'}
icecandidate message{type:'new-ice-candidate',target:'callee',candidate:'signal data'}

connection flow
A:createOffer() ,send offer message to signal server
M:receive Offer message
M:Send Offer message to B
B:create connection ,setRemoteDescription(..)
B:create answer to Signal server
M:receive answer message
M:send answer message to A
A:set remoteDescription


next ICE message sess https://mdn.mozillademos.org/files/12365/WebRTC%20-%20ICE%20Candidate%20Exchange.svg
A:send new ice candidate message
M:receive new ice candidate message
M:send ice candidate message to B
B:handle new ice candidate  (add candidate to connection )
B:send new ice candidate message
M:receive new ice candidate message
M:send ice candidate message to A
A:handle new ice candidate (add candidate to connection)


 */
@ServerEndpoint("/p2p")
@Component
public class P2PWebsocketServer {

    static Logger logger = LoggerFactory.getLogger(P2PWebsocketServer.class);

    static Map<String, Session> sessions = new ConcurrentReferenceHashMap<>();


    @OnOpen
    public void onOpen(Session session)     {
        logger.info("new session connected {} ,session size {}", session.getId(), sessions.size());
        session.getUserProperties().put("name",
                session.getRequestParameterMap().getOrDefault("name", Arrays.asList(session.getId())).get(0));


        JSONObject msg = getFriendsMsg(session.getId());
        try {
            session.getBasicRemote().sendText(msg.toJSONString());
        } catch (IOException e) {

        }
        sessions.put(session.getId(), session);
        notifyNewUser(session);
    }

    JSONObject getFriendsMsg(String id) {
        JSONArray users = getOnLineUsers(id);
        JSONObject msg = new JSONObject();
        msg.put("type", "getFriends");
        msg.put("friends", users);
        msg.put("id", id);
        return msg;
    }

    void notifyNewUser(Session newSession) {
        JSONObject friend = new JSONObject();
        friend.put("name", newSession.getUserProperties().getOrDefault("name", newSession.getId()));
        friend.put("id", newSession.getId());

        sessions.forEach((id, s) -> {
            if (s.isOpen()) {

                if (!id.equals(newSession.getId())) {
                    JSONObject online = new JSONObject();
                    online.put("type", "online");
                    online.put("friend", friend);
                    try {
                        s.getBasicRemote().sendText(JSON.toJSONString(online));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    JSONArray getOnLineUsers(String excludeId) {
        JSONArray jsonArray = new JSONArray();
        for (Session s : sessions.values()) {
            if (!s.isOpen()) continue;
            if (s.getId() == excludeId) continue;
            JSONObject friend = new JSONObject();
            friend.put("name", s.getUserProperties().get("name"));
            friend.put("id", s.getId());
            jsonArray.add(friend);
        }
        return jsonArray;
    }


    @OnClose
    public void onClose(Session session)     {

        onLeave(session);
        sessions.remove(session);
        logger.info("  session closed {} {}", session.getId(), sessions.size());
    }


    @OnError
    public void onError(Session session, Throwable ex) {
        sessions.remove(session);
        logger.error("  session error {} {}", session.getId(), ex.getMessage(), ex);
        onLeave(session);
    }

    private void onLeave(Session session) {

        for (Session s : sessions.values()) {
            if (!s.isOpen()) continue;
            JSONObject d = new JSONObject();
            d.put("name", session.getUserProperties().getOrDefault("name", s.getId()));
            d.put("id", session.getId());

            JSONObject leave = new JSONObject();
            leave.put("type", "leave");
            leave.put("friend", d);
            try {
                s.getBasicRemote().sendText(JSON.toJSONString(leave));
            } catch (Exception ex) {
            }
        }
    }

    @OnMessage
    public void onMessage(Session session, String msg)     {
        logger.info("receive message from {} ,data {}", session.getId(), msg);
        JSONObject json = JSON.parseObject(msg);
        String type = json.getString("type");
        switch (type) {
            case "getFriends":
                JSONObject data = getFriendsMsg(session.getId());
                parseData(json, type);
                break;

            case "icecandidate":
                parseData(json, type);
                break;
            case "offer":
                parseData(json, type);
                break;
            case "answer":
                parseData(json, type);
                break;

        }
    }

    private void parseData(JSONObject json, String key)     {
        Session target = sessions.get(json.getString("target"));
        if (target == null) {
            logger.error("target is not found !!!");
            return;
        }
        JSONObject candidate = new JSONObject();
        candidate.put("target", json.getString("id"));
        candidate.put("type", key);
        candidate.put(key, json.getString(key));

        String data = JSON.toJSONString(candidate);
        logger.info("send data {}", data);
        try {
            target.getBasicRemote().sendText(data);
        } catch (IOException e) {

        }

    }


    void onCreateOffer() {
    }


    void onIceChange() {

    }


}
